package dao

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"sync"
	"time"

	"github.com/xormplus/xorm"
	"github.com/xormplus/xorm/caches"
	"gopkg.in/yaml.v2"
	"xorm.io/core"
)

type xormDB struct {
	Xorm *xormConfig `yaml:"xorm"`
}

type xormConfig struct {
	DriverName string          `yaml:"driver-name"`
	DataSource *xormDataSource `yaml:"data-source"`
	Cache      *xormCache      `yaml:"cache"`
	ShowSQL    bool            `yaml:"show-sql"`
}

type xormCache struct {
	Enabled           bool          `yaml:"enabled"`
	ExpiredTimeSecond time.Duration `yaml:"expired-time-second"`
	MaxElementSize    int           `yaml:"max-element-size"`
}

type xormDataSource struct {
	Master string   `yaml:"master"`
	Slaves []string `yaml:"slaves"`
}

type Xorm struct {
	*xorm.Engine
}

var once sync.Once
var engineGroup *xorm.EngineGroup
var xormObjectCache map[string]*Xorm = make(map[string]*Xorm)

func Default() *Xorm {
	return xormObjectCache[fmtMasterKey()]
}

func Master() *Xorm {
	return xormObjectCache[fmtMasterKey()]
}

func Slave(index int) *Xorm {
	return xormObjectCache[fmtSlaveKey(index)]
}

func (xorm *Xorm) DoInTransaction(closure func(session *xorm.Session) error) error {
	session := xorm.NewSession()
	defer session.Close()
	session.Begin()
	err := closure(session)
	if nil != err {
		session.Rollback()
		return err
	}
	session.Commit()
	return nil
}

func ChangeMasterMock(db *sql.DB) {
	engineGroup.Main().DB().DB = db
}

func InitXormEngine(config string, policies ...xorm.GroupPolicy) error {
	var err error
	once.Do(func() {
		jsonFile, err := ioutil.ReadFile(config)
		if err != nil {
			return
		}

		var xormDB *xormDB
		err = yaml.Unmarshal(jsonFile, &xormDB)
		if err != nil {
			return
		}

		master, err := xorm.NewEngine(xormDB.Xorm.DriverName, xormDB.Xorm.DataSource.Master)
		if err != nil {
			return
		}

		slaves := make([]*xorm.Engine, len(xormDB.Xorm.DataSource.Slaves))
		for index, slaveUrl := range xormDB.Xorm.DataSource.Slaves {
			slave, err := xorm.NewEngine(xormDB.Xorm.DriverName, slaveUrl)
			if err != nil {
				return
			}
			slaves[index] = slave
		}

		var policy xorm.GroupPolicy
		if len(policies) > 0 {
			policy = policies[0]
		} else {
			policy = xorm.RoundRobinPolicy()
		}
		engineGroup, err = xorm.NewEngineGroup(master, slaves, policy)
		if nil != err {
			return
		}
		if xormDB.Xorm.Cache.Enabled {
			cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), xormDB.Xorm.Cache.ExpiredTimeSecond*1000*1000, xormDB.Xorm.Cache.MaxElementSize)
			engineGroup.SetDefaultCacher(cacher)
		}

		engineGroup.ShowSQL(xormDB.Xorm.ShowSQL)
		engineGroup.SetMapper(core.SameMapper{})
		engineGroup.SetTableMapper(core.NewPrefixMapper(core.SnakeMapper{}, "t_"))
		engineGroup.SetColumnMapper(core.SnakeMapper{})

		xormObjectCache[fmtMasterKey()] = &Xorm{Engine: engineGroup.Main()}
		for index, v := range engineGroup.Subordinates() {
			xormObjectCache[fmtSlaveKey(index)] = &Xorm{Engine: v}
		}
	})
	return err
}

func fmtSlaveKey(index int) string {
	return fmt.Sprintf("SLAVE-%d", index)
}

func fmtMasterKey() string {
	return "MASTER"
}
